package com.ruoyi.web.runner;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.protobuf.ByteString;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.web.constant.RedisKeyPrefixConstant;
import com.ruoyi.web.constant.RedisMessageType;
import com.ruoyi.web.constant.WalletConstant;
import com.ruoyi.web.domain.Currency;
import com.ruoyi.web.domain.CurrencyBlock;
import com.ruoyi.web.domain.MiningConfig;
import com.ruoyi.web.domain.RechargeRecord;
import com.ruoyi.web.domain.UserAddress;
import com.ruoyi.web.domain.UserShareRecord;
import com.ruoyi.web.dto.TronContractsEventsTransfer;
import com.ruoyi.web.enums.CommonDictEnum;
import com.ruoyi.web.enums.RechargeRecordEnum;
import com.ruoyi.web.enums.UserEnums;
import com.ruoyi.web.mapper.MiningConfigMapper;
import com.ruoyi.web.mapper.UserShareRecordMapper;
import com.ruoyi.web.service.ICurrencyBlockService;
import com.ruoyi.web.service.ICurrencyService;
import com.ruoyi.web.service.IRechargeRecordService;
import com.ruoyi.web.service.IUserAddressService;
import com.ruoyi.web.utils.IdWorker;
import io.reactivex.Flowable;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.tron.trident.core.ApiWrapper;
import org.tron.trident.utils.Base58Check;
import org.web3j.abi.EventEncoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.request.EthFilter;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.EthBlockNumber;
import org.web3j.protocol.core.methods.response.EthLog;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Convert;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 项目启动初始化参数
 *
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ApplicationRunnerImpl implements ApplicationRunner {

    public final RedisCache redis;
    private final OkHttpClient okHttpClient = new OkHttpClient();

    private final ICurrencyBlockService currentBlockService;

    private final ICurrencyService currencyService;

    private final IUserAddressService addressService;

    private final IRechargeRecordService rechargeRecordService;
    private final UserShareRecordMapper userShareRecordMapper;

    private final IdWorker idWorker;

    private final MiningConfigMapper configMapper;

    @Resource(name = "ethThreadPoolExecutor")
    private ThreadPoolExecutor ethThreadPoolExecutor;

    @Resource(name = "bscThreadPoolExecutor")
    private ThreadPoolExecutor bscThreadPoolExecutor;

    @Resource(name = "tronThreadPoolExecutor")
    private ThreadPoolExecutor tronThreadPoolExecutor;

    @Override
    public void run(ApplicationArguments args) {
        initRedis();
//        ethThreadPoolExecutor.execute(this::eth);
//        bscThreadPoolExecutor.execute(this::bsc);
//        tronThreadPoolExecutor.execute(this::tron);
    }

    /**
     * 每日上传次数 - 处理用户分享记录旧数据
     *
     * @since 2023/4/3 14:25
     */
    public void updateUserShareRecord() {
        List<UserShareRecord> list = userShareRecordMapper.selectUserShareRecordList(new UserShareRecord());
        list.forEach(i -> {
            if (userShareRecordMapper.handleUploadCount(i.getId()) != 1) {
                log.error("处理用户分享记录旧数据失败，id：{}", i.getId());
            }
        });
        log.error("处理用户分享记录旧数据完成，总计：{} 条", list.size());
    }

    public void initRedis() {
        List<MiningConfig> configs = configMapper.selectMiningConfigList(new MiningConfig());
        if (configs.size() == 0) {
            return;
        }
        redis.setCacheObject(RedisMessageType.MINING_CONFIG, configs.get(0));
    }

    private void bsc() {
        eth(UserEnums.Chain.BSC.getValue());
    }

    private void eth() {
        eth(UserEnums.Chain.ETH.getValue());
    }

    public void eth(String chain) {
        // 获取UNT 币种
        List<Currency> currencies = currencyService.selectCurrencyList(new Currency() {{
            setName(WalletConstant.USDT);
            setPublicChain(chain);
            setState(CommonDictEnum.Status.NORMAL.getValue());
        }});
        Currency currencyServiceOne = currencies.size() > 0 ? currencies.get(0) : null;
        if (currencyServiceOne == null) {
            return;
        }
        Web3j web3j = Web3j.build(new HttpService(currencyServiceOne.getNodeAddress() + currencyServiceOne.getApiKey()));

        // 连接USC-API
        // 获取区块
        List<CurrencyBlock> blocks = currentBlockService.selectCurrencyBlockList(new CurrencyBlock() {{
            setPublicChain(chain);
        }});
        CurrencyBlock currentBlock = blocks.size() > 0 ? blocks.get(0) : null;
        // 初始化区块
        if (currentBlock == null) {
            org.web3j.protocol.core.Request<?, EthBlockNumber> ethBlockNumberRequest = web3j.ethBlockNumber();
            EthBlockNumber send;
            try {
                send = ethBlockNumberRequest.send();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            BigInteger blockNumber = send.getBlockNumber();
            currentBlock = new CurrencyBlock();
            currentBlock.setId(idWorker.nextId());
            currentBlock.setPublicChain(chain);
            currentBlock.setBlockNumber(String.valueOf(blockNumber));
            currentBlockService.insertCurrencyBlock(currentBlock);
        }
        final String[] blockNumber = {currentBlock.getBlockNumber()};

        Flowable<EthBlock> ethBlockFlowable = web3j.replayPastAndFutureBlocksFlowable(
                DefaultBlockParameter.valueOf(BigInteger.valueOf(Long.parseLong(blockNumber[0]))),
                false);

        CurrencyBlock currentBlockUpdate = new CurrencyBlock();
        currentBlockUpdate.setId(currentBlock.getId());

        ethBlockFlowable.blockingSubscribe(ethBlock -> {
            EthFilter filter = new EthFilter(ethBlock.getBlock().getHash(), currencyServiceOne.getSmartContractAddress());
            Event event = new Event("Transfer",
                    Arrays.<TypeReference<?>>asList(
                            new TypeReference<Address>(true) {
                            },
                            new TypeReference<Address>(true) {
                            },
                            new TypeReference<Uint256>(false) {
                            }));
            filter.addSingleTopic(EventEncoder.encode(event));
            org.web3j.protocol.core.Request<?, EthLog> ethLogRequest = web3j.ethGetLogs(filter);
            EthLog send = ethLogRequest.send();
            List<EthLog.LogResult> logs = send.getLogs();
            if (CollUtil.isNotEmpty(logs)) {
                logs.forEach(logResult -> {
                    EthLog.LogObject logObject = (EthLog.LogObject) logResult.get();
                    // log.info("eth区块高度：{},交易记录：{}", ethBlock.getBlock().getNumber(), JSONUtil.parseObj(logObject));
                    String toReplace = StrUtil.replace(logObject.getTopics().get(2), 2, 26, "");
                    // 获取用户钱包地址

                    List<UserAddress> list = addressService.selectUserAddressList(new UserAddress() {{
                        setChain(chain);
                    }});
                    if (CollUtil.isNotEmpty(list)) {
                        List<String> addressList = list.stream().map(UserAddress::getAddress).collect(Collectors.toList());
                        boolean contains = CollUtil.contains(addressList, toReplace);
                        if (contains) {
                            log.info(chain + "地址匹配成功,交易哈希：{}", logObject.getTransactionHash());

                            List<RechargeRecord> records = rechargeRecordService.selectRechargeRecordList(new RechargeRecord() {{
                                setTransactionHash(logObject.getTransactionHash());
                            }});
                            if (records.size() > 0) {
                                log.error(chain + "存在相同交易哈希：{}", logObject.getTransactionHash());
                                return;
                            }

                            // 获取充值用户
                            List<UserAddress> collect = list.stream().filter(address -> StrUtil.equals(address.getAddress(), toReplace)).collect(Collectors.toList());
                            if (collect.size() < 1) {
                                return;
                            }
                            UserAddress userAddress = collect.get(0);
                            // 获取充值数量
                            String valueStr = new BigInteger(logObject.getData().substring(2), 16).toString();
                            BigDecimal balance = Convert.fromWei(new BigDecimal(valueStr), Convert.Unit.MWEI).setScale(Math.toIntExact(currencyServiceOne.getAccuracy()), RoundingMode.DOWN);
                            // 充值记录
                            RechargeRecord rechargeRecord = new RechargeRecord();
                            rechargeRecord.setId(idWorker.nextId());
                            // 公链
                            rechargeRecord.setPublicChain(chain);
                            // 账户地址id
                            rechargeRecord.setAddressId(userAddress.getId());
                            // 用户id
                            rechargeRecord.setUserId(userAddress.getUserId());
                            // 区块
                            rechargeRecord.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
                            // 充值数量
                            rechargeRecord.setQuantity(balance);
                            // 到账地址
                            rechargeRecord.setArrivalAddress(toReplace);
                            // 来源地址
                            String fromReplace = StrUtil.replace(logObject.getTopics().get(1), 2, 26, "");
                            rechargeRecord.setSourceAddress(fromReplace);
                            // 区块确认数
                            rechargeRecord.setBlockConfirmationNumber("0");
                            // 交易哈希
                            rechargeRecord.setTransactionHash(logObject.getTransactionHash());
                            // 充值区块确认数
                            rechargeRecord.setBlockConfirmationRechargeNumber(currencyServiceOne.getConfirmationBlockRechargeNumber());
                            // 最小充值数
                            rechargeRecord.setMinimumRechargeNumber(currencyServiceOne.getMinimumRechargeAmount());
                            // 手续费
                            rechargeRecord.setServiceCharge(currencyServiceOne.getServiceCharge());
                            // 币种
                            rechargeRecord.setCurrency(currencyServiceOne.getName());
                            // 状态
                            rechargeRecord.setState(RechargeRecordEnum.Status.RECHARGING.getValue());
                            // 充值类型
                            rechargeRecord.setType(RechargeRecordEnum.Type.CHAIN_RECHARGE.getValue());
                            rechargeRecordService.insertRechargeRecord(rechargeRecord);
                            // 存入redis等待校验
                            redis.setCacheMapValue(RedisKeyPrefixConstant.RECHARGE_RECORD_CHECK, rechargeRecord.getId().toString(), 0);
                        }
                    }
                });
            }
            currentBlockUpdate.setBlockNumber(String.valueOf(ethBlock.getBlock().getNumber()));
            currentBlockService.updateCurrencyBlock(currentBlockUpdate);
        }, throwable -> {
            throw new RuntimeException(throwable.getMessage());
        });
    }

    public void tron() {
        // 获取当前区块
        List<CurrencyBlock> blocks = currentBlockService.selectCurrencyBlockList(new CurrencyBlock() {{
            setPublicChain(UserEnums.Chain.TRX.getValue());
        }});
        CurrencyBlock currentBlock = blocks.size() > 0 ? blocks.get(0) : null;
        // 获取UNT 币种
        List<Currency> currencies = currencyService.selectCurrencyList(new Currency() {{
            setName(WalletConstant.USDT);
            setPublicChain(UserEnums.Chain.TRX.getValue());
            setState(CommonDictEnum.Status.NORMAL.getValue());
        }});
        Currency currency = currencies.size() > 0 ? currencies.get(0) : null;
        if (currency == null) {
            return;
        }
        if (currentBlock == null) {

            currentBlock = new CurrencyBlock();
            currentBlock.setId(idWorker.nextId());
            currentBlock.setPublicChain(UserEnums.Chain.TRX.getValue());
            currentBlock.setBlockNumber(String.valueOf(this.getLastBlockNumber(currency)));
            currentBlockService.insertCurrencyBlock(currentBlock);
        }

        String blockNumber = currentBlock.getBlockNumber();
        CurrencyBlock currentBlockUpdate = new CurrencyBlock();
        currentBlockUpdate.setId(currentBlock.getId());
        do {
            try {
                if (this.getLastBlockNumber(currency).compareTo(new BigDecimal(blockNumber)) <= 0) {
                    TimeUnit.MILLISECONDS.sleep(2000);
                    continue;
                }
                Request request = new Request.Builder()
                        .url(currency.getNodeAddress() + "v1/contracts/" +
                                currency.getSmartContractAddress() + "/events?event_name=Transfer&block_number=" +
                                blockNumber + "&only_confirmed=true&limit=200")
                        .get()
                        .addHeader("accept", "application/json")
                        .build();
                this.tronNext(request, currency);
                BigDecimal add = NumberUtil.add(blockNumber, "1");
                blockNumber = String.valueOf(add);
                currentBlockUpdate.setBlockNumber(String.valueOf(add));
                currentBlockService.updateCurrencyBlock(currentBlockUpdate);

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        } while (true);
    }

    public void tronNext(Request request, Currency currency) {
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String res = null;
        try {
            res = response.body().string();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        TronContractsEventsTransfer tronContractsEventsTransfer = JSONUtil.toBean(res, TronContractsEventsTransfer.class);
        if (tronContractsEventsTransfer.getData() == null) {
            return;
        }
        tronContractsEventsTransfer.getData().forEach(o -> {
            // 用户列表
            List<UserAddress> accountList = addressService.selectUserAddressList(new UserAddress() {{
                setChain(UserEnums.Chain.TRX.getValue());
            }});
            if (CollUtil.isNotEmpty(accountList)) {
                // 获取所有用户地址
                List<String> addressList = accountList.stream().map(UserAddress::getAddress).collect(Collectors.toList());
                String toRes = o.getResult().getTo();
                String replace = StrUtil.replace(toRes, 0, 2, "41");
                ByteString bytes = ApiWrapper.parseAddress(replace);
                String tronAddress = Base58Check.bytesToBase58(bytes.toByteArray());

                if (CollUtil.contains(addressList, tronAddress)) {
                    log.info("tron地址匹配成功,交易哈希：{}", o.getTransactionId());

                    List<RechargeRecord> records = rechargeRecordService.selectRechargeRecordList(new RechargeRecord() {{
                        setTransactionHash(o.getTransactionId());
                    }});
                    if (records.size() > 0) {
                        log.error("tron存在相同交易哈希：{}", o.getTransactionId());
                        return;
                    }
                    UserAddress userAddress = accountList.stream().filter(account -> StrUtil.equals(account.getAddress(), tronAddress)).findFirst().get();
                    // 充值记录
                    RechargeRecord rechargeRecord = new RechargeRecord();
                    rechargeRecord.setId(idWorker.nextId());
                    // 公链
                    rechargeRecord.setPublicChain(UserEnums.Chain.TRX.getValue());
                    // 账户地址id
                    rechargeRecord.setAddressId(userAddress.getId());
                    // 用户id
                    rechargeRecord.setUserId(userAddress.getUserId());
                    // 区块
                    rechargeRecord.setBlockNumber(String.valueOf(o.getBlockNumber()));
                    // 充值数量
                    BigDecimal bigDecimal = NumberUtil.toBigDecimal(o.getResult().getValue()).movePointLeft(6);
                    rechargeRecord.setQuantity(bigDecimal);
                    // 到账地址
                    rechargeRecord.setArrivalAddress(tronAddress);
                    // 来源地址
                    String fromRes = o.getResult().getFrom();
                    String fromRep = StrUtil.replace(fromRes, 0, 2, "41");
                    ByteString fromBytes = ApiWrapper.parseAddress(fromRep);
                    String fromAddress = Base58Check.bytesToBase58(fromBytes.toByteArray());
                    rechargeRecord.setSourceAddress(fromAddress);
                    // 区块确认数
                    rechargeRecord.setBlockConfirmationNumber("0");
                    // 交易哈希
                    rechargeRecord.setTransactionHash(o.getTransactionId());
                    // 充值区块确认数
                    rechargeRecord.setBlockConfirmationRechargeNumber(currency.getConfirmationBlockRechargeNumber());
                    // 最小充值数
                    rechargeRecord.setMinimumRechargeNumber(currency.getMinimumRechargeAmount());
                    // 手续费
                    rechargeRecord.setServiceCharge(currency.getServiceCharge());
                    // 币种
                    rechargeRecord.setCurrency(currency.getName());
                    // 状态
                    rechargeRecord.setState(RechargeRecordEnum.Status.RECHARGING.getValue());
                    // 充值类型
                    rechargeRecord.setType(RechargeRecordEnum.Type.CHAIN_RECHARGE.getValue());
                    rechargeRecordService.insertRechargeRecord(rechargeRecord);
                    // 存入redis等待校验
                    redis.setCacheMapValue(RedisKeyPrefixConstant.RECHARGE_RECORD_CHECK, rechargeRecord.getId().toString(), 0);

                }
            }

        });


    }

    /**
     * 获取波场区块
     *
     * @param currency
     * @return
     */
    public BigDecimal getLastBlockNumber(Currency currency) {
        // 查询最新block
        Request request = new Request.Builder()
                .url(currency.getNodeAddress() + "walletsolidity/getnowblock")
                .get()
                .addHeader("Accept", "application/json")
                .build();

        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String res = null;
        try {
            res = response.body().string();
            String blockNumber = JSONUtil.parseObj(res).getJSONObject("block_header").getJSONObject("raw_data").getStr("number");
            return new BigDecimal(blockNumber);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


}
